/*
** Copyright (c) 2008 Mobile GCalendar
**
** Permission is hereby granted, free of charge, to any person
** obtaining a copy of this software and associated documentation
** files (the "Software"), to deal in the Software without
** restriction, including without limitation the rights to use,
** copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the
** Software is furnished to do so, subject to the following
** conditions:
**
** The above copyright notice and this permission notice shall be
** included in all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
** OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
** HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
** OTHER DEALINGS IN THE SOFTWARE.
*/

package au.edu.mq.comp.itec800.mgc.lib.calendar.event;

import java.security.InvalidParameterException;
import java.util.Calendar;
import java.util.Date;
import java.util.regex.Pattern;

import au.edu.mq.comp.itec800.mgc.util.StringUtils;


/**
 * @author Laurent Malvert [laurent.malvert@students.mq.edu.au]
 *
 */
public class DateRange
{
  private static final int[]    calendarFields  = new int[] {
    Calendar.YEAR,
    Calendar.MONTH,
    Calendar.DAY_OF_MONTH,
    Calendar.HOUR_OF_DAY,
    Calendar.MINUTE,
    Calendar.SECOND
  };
  private static final Pattern  dateParser      = Pattern.compile("\\d{4}([-./]\\d{2}([-./]\\d{2}" +
                                                                  "(T\\d{2}(:\\d{2}(:\\d{2})?)?)?)?)?");

  private Date  start = null;
  private Date  end   = null;

  public DateRange(final String start, final String end)
  {
    this(date(start), date(end));
  }

  public DateRange(final Date start, final Date end)
  {
    if ((start == null) || (end == null))
    {
      throw new InvalidParameterException("range dates cannot be null");
    }
    if (start.after(end))
    {
      throw new InvalidParameterException("end date must follow start date");
    }
    this.start  = start;
    this.end    = end;
  }

  public Date               getStart()
  {
    return (start);
  }

  public Date               getEnd()
  {
    return (end);
  }

  public boolean            isAtomic()
  {
    return (getStart().equals(getEnd()));
  }

  public boolean            has(final Date date)
  {
    final Date              d = (date != null) ? date : now();

    return ((getStart().before(d) && getEnd().after(d)) ||
            (d.equals(getStart()) || d.equals(getEnd())));
  }

  @Override
  public String             toString()
  {
    return (getStart().toString() + " - " + getEnd().toString());
  }

  public static DateRange   minute()
  {
    return (minute(null));
  }

  public static DateRange   minute(Date start)
  {
    final Calendar          cal   = Calendar.getInstance();
    final Date              end;

    if (start == null)
    {
      start = cal.getTime();
    }
    end = nextMinute(start);
    return (new DateRange(start, end));
  }

  public static DateRange   minutes(final int minutes)
  {
    return (minutes(null, minutes));
  }

  public static DateRange   minutes(Date start, final int minutes)
  {
    final Calendar          cal   = Calendar.getInstance();
    final Date              end;

    if (start == null)
    {
      start = cal.getTime();
    }
    end = jumpToMinutes(start, minutes);
    return (new DateRange(start, end));
  }

  public static DateRange   hour()
  {
    return (hour(null));
  }

  public static DateRange   hour(Date start)
  {
    final Calendar          cal   = Calendar.getInstance();
    final Date              end;

    if (start == null)
    {
      start = cal.getTime();
    }
    end = nextHour(start);
    return (new DateRange(start, end));
  }

  public static DateRange   day()
  {
    return (day(null));
  }

  public static DateRange   day(Date start)
  {
    final Calendar          cal   = Calendar.getInstance();
    final Date              end;

    if (start == null)
    {
      start = cal.getTime();
    }
    end = nextDay(start);
    return (new DateRange(start, end));
  }

  public static DateRange   week()
  {
    return (week(null));
  }

  public static DateRange   week(Date start)
  {
    final Calendar          cal   = Calendar.getInstance();
    final Date              end;

    if (start == null)
    {
      start = cal.getTime();
    }
    end = nextWeek(start);
    return (new DateRange(start, end));
  }

  public static DateRange   month()
  {
    return (month(null));
  }

  public static DateRange   month(Date start)
  {
    final Calendar          cal   = Calendar.getInstance();
    final Date              end;

    if (start == null)
    {
      start = cal.getTime();
    }
    end = nextMonth(start);
    return (new DateRange(start, end));
  }

  public static DateRange   year()
  {
    return (year(null));
  }

  public static DateRange   year(Date start)
  {
    final Calendar          cal   = Calendar.getInstance();
    final Date              end;

    if (start == null)
    {
      start = cal.getTime();
    }
    end = nextYear(start);
    return (new DateRange(start, end));
  }


  public static Date        nextSecond()
  {
    return (nextSecond(now()));
  }

  public static Date        nextSecond(final Date start)
  {
    return (jumpToSeconds(start, 1));
  }

  public static Date        jumpToSeconds(final int seconds)
  {
    return (jumpToDays(now(), seconds));
  }

  public static Date        jumpToSeconds(final Date start, final int seconds)
  {
    return (jump(start, Calendar.SECOND, seconds));
  }


  public static Date        nextMinute()
  {
    return (nextMinute(now()));
  }

  public static Date        nextMinute(final Date start)
  {
    return (jumpToMinutes(start, 1));
  }

  public static Date        jumpToMinutes(final int minutes)
  {
    return (jumpToDays(now(), minutes));
  }

  public static Date        jumpToMinutes(final Date start, final int minutes)
  {
    return (jump(start, Calendar.MINUTE, minutes));
  }


  public static Date        nextHour()
  {
    return (nextHour(now()));
  }

  public static Date        nextHour(final Date start)
  {
    return (jumpToHours(start, 1));
  }

  public static Date        jumpToHours(final int hours)
  {
    return (jumpToDays(now(), hours));
  }

  public static Date        jumpToHours(final Date start, final int hours)
  {
    return (jump(start, Calendar.HOUR, hours));
  }


  public static Date        nextDay()
  {
    return (nextDay(now()));
  }

  public static Date        nextDay(final Date start)
  {
    return (jumpToDays(start, 1));
  }

  public static Date        jumpToDays(final int days)
  {
    return (jumpToDays(now(), days));
  }

  public static Date        jumpToDays(final Date start, final int days)
  {
    return (jump(start, Calendar.DAY_OF_MONTH, days));
  }


  public static Date        nextWeek()
  {
    return (nextWeek(now()));
  }

  public static Date        nextWeek(final Date start)
  {
    return (jumpToWeeks(start, 1));
  }

  public static Date        jumpToWeeks(final int weeks)
  {
    return (jumpToWeeks(now(), weeks));
  }

  public static Date        jumpToWeeks(final Date start, final int weeks)
  {
    return (jump(start, Calendar.WEEK_OF_YEAR, weeks));
  }


  public static Date        nextMonth()
  {
    return (nextMonth(now()));
  }

  public static Date        nextMonth(final Date start)
  {
    return (jumpToMonths(start, 1));
  }

  public static Date        jumpToMonths(final int months)
  {
    return (jumpToMonths(now(), months));
  }

  public static Date        jumpToMonths(final Date start, final int months)
  {
    return (jump(start, Calendar.MONTH, months));
  }


  public static Date        nextYear()
  {
    return (nextYear(now()));
  }

  public static Date        nextYear(final Date start)
  {
    return (jumpToYears(start, 1));
  }

  public static Date        jumpToYears(final int years)
  {
    return (jumpToYears(now(), years));
  }

  public static Date        jumpToYears(final Date start, final int years)
  {
    return (jump(start, Calendar.YEAR, years));
  }


  private static Date       jump(final Date start,
                                 final int dateField,
                                 final int dateFieldIncrement)
  {
    final Calendar          cal = calendar(start);

    cal.add(dateField, dateFieldIncrement);
    return (cal.getTime());
  }


  public static Date        now()
  {
    return (new Date());
  }


  public static Calendar    calendar()
  {
    return (calendar(null));
  }

  public static Calendar    calendar(final Date choice)
  {
    final Calendar          cal = Calendar.getInstance();

    cal.setTime((choice != null) ? choice : now());
    return (cal);
  }

  public static Calendar    dcalendar()
  {
    return (dcalendar(null));
  }

  public static Calendar    dcalendar(final Date choice)
  {
    final Calendar          cal = calendar(choice);

    cal.clear(Calendar.HOUR);
    cal.clear(Calendar.MINUTE);
    cal.clear(Calendar.SECOND);
    cal.set(Calendar.HOUR_OF_DAY, 0);
    return (cal);
  }

  public static Calendar    wcalendar()
  {
    return (wcalendar(null));
  }

  public static Calendar    wcalendar(final Date choice)
  {
    final Calendar          cal = dcalendar(choice);

    cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek());
    return (cal);
  }

  public static Calendar    mcalendar()
  {
    return (mcalendar(null));
  }

  public static Calendar    mcalendar(final Date choice)
  {
    final Calendar          cal = dcalendar(choice);

    cal.set(Calendar.DAY_OF_MONTH, 1);
    return (cal);
  }

  public static Date        date(String date)
  {
    Date                    d = new Date();

    if (date != null)
    {
      date = StringUtils.clean(date);
      if (dateParser.matcher(date).matches() == false)
      {
        throw new InvalidParameterException("invalid date format");
      }
      final String[]          fields  = date.split("[-./:T]");
      final Calendar          cal     = calendar();
      int                     idx     = 0;

      cal.clear();
      for (final String fld : fields)
      {
        final int field = calendarFields[idx++];

        cal.set(field,
                Integer.parseInt(fld) - ((field == Calendar.MONTH) ? 1 : 0));
      }
      d.setTime(cal.getTimeInMillis());
    }
    return (d);
  }

  public static DateRange   range(final String start, final String end)
  {
    return (new DateRange(start, end));
  }

  public static String      toStamp(final Date date)
  {
    return (String.format("%1$tY.%1$tm.%1$tdT%1$tH:%1$tM", (date != null) ? date : now()));
  }

  public static void        main(final String[] args)
  {
    final Date              now = now();

    System.out.println("Date: " + now + " - " + nextDay(now));
    System.out.println("Date: " + now + " - " + nextWeek(now));
    System.out.println("Date: " + now + " - " + nextMonth(now));
    System.out.println("Date: " + now + " - " + nextYear(now));
    System.out.println("day: " + DateRange.day());
    System.out.println("week: " + DateRange.week());
    System.out.println("month: " + DateRange.month());
    System.out.println("year: " + DateRange.year());
    System.out.println("range: " + DateRange.range("2008.06.28T15:30:45", "2008.07.01T15:36:47"));
  }
}
